package cn.iocoder.yudao.module.infra.controller.admin.file;

import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import cn.hutool.crypto.digest.DigestUtil;
import cn.hutool.json.JSONObject;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.*;
import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileDO;
import cn.iocoder.yudao.module.infra.framework.file.core.utils.FileMediaUtils;
import cn.iocoder.yudao.module.infra.service.file.FileService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.annotation.security.PermitAll;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.request.async.DeferredResult;
import org.springframework.web.multipart.MultipartFile;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CompletableFuture;

import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.module.infra.framework.file.core.utils.FileTypeUtils.writeAttachment;

@Tag(name = "管理后台 - 文件存储")
@RestController
@RequestMapping("/infra/file")
@Validated
@Slf4j
public class FileController {

    @Resource
    private FileService fileService;

    @PostMapping("/upload")
    @Operation(summary = "上传文件", description = "模式一：后端上传文件")
    public CommonResult<String> uploadFile(FileUploadReqVO uploadReqVO) throws Exception {
        MultipartFile file = uploadReqVO.getFile();
        String path = uploadReqVO.getPath();
        return success(fileService.createFile(file.getOriginalFilename(), path, IoUtil.readBytes(file.getInputStream())));
    }

    @PostMapping("/upload/async")
    @Operation(summary = "异步上传文件", description = "异步上传，立即返回处理中状态，适合大文件上传")
    public DeferredResult<CommonResult<Map<String, Object>>> uploadFileAsync(FileUploadReqVO uploadReqVO) throws Exception {
        DeferredResult<CommonResult<Map<String, Object>>> deferredResult = new DeferredResult<>();
        MultipartFile file = uploadReqVO.getFile();
        String md5 = DigestUtil.md5Hex(file.getInputStream());
        // 先返回上传中的状态
        Map<String, Object> initialResponse = new HashMap<>();
        initialResponse.put("status", "uploading");
        initialResponse.put("md5", md5);
        
        // 检查是否已存在
        String existingUrl = fileService.getFileUrlByMD5(md5);
        if (StringUtils.isNotEmpty(existingUrl)) {
            Map<String, Object> response = new HashMap<>();
            response.put("status", "success");
            response.put("url", existingUrl);
            response.put("md5", md5);
            deferredResult.setResult(success(response));
            return deferredResult;
        }
        
        // 异步执行上传
        byte[] content = IoUtil.readBytes(file.getInputStream());
        String path = uploadReqVO.getPath();
        boolean autoCompress = uploadReqVO.getAutoCompress() == null || uploadReqVO.getAutoCompress();
        CompletableFuture<String> future = fileService.asyncCreateFile(file.getOriginalFilename(), path, content, autoCompress);
        
        // 设置回调
        future.thenAccept(url -> {
            Map<String, Object> response = new HashMap<>();
            response.put("status", "success");
            response.put("url", url);
            response.put("md5", md5);
            deferredResult.setResult(success(response));
        }).exceptionally(ex -> {
            Map<String, Object> response = new HashMap<>();
            response.put("status", "error");
            response.put("message", ex.getMessage());
            deferredResult.setResult(success(response));
            return null;
        });
        
        return deferredResult;
    }

    @GetMapping("/presigned-url")
    @Operation(summary = "获取文件预签名地址", description = "模式二：前端上传文件：用于前端直接上传七牛、阿里云 OSS 等文件存储器")
    public CommonResult<FilePresignedUrlRespVO> getFilePresignedUrl(@RequestParam("path") String path) throws Exception {
        return success(fileService.getFilePresignedUrl(path));
    }

    @PostMapping("/create")
    @Operation(summary = "创建文件", description = "模式二：前端上传文件：配合 presigned-url 接口，记录上传了上传的文件")
    public CommonResult<Long> createFile(@Valid @RequestBody FileCreateReqVO createReqVO) {
        return success(fileService.createFile(createReqVO));
    }

    @DeleteMapping("/delete")
    @Operation(summary = "删除文件")
    @Parameter(name = "id", description = "编号", required = true)
    @PreAuthorize("@ss.hasPermission('infra:file:delete')")
    public CommonResult<Boolean> deleteFile(@RequestParam("id") Long id) throws Exception {
        fileService.deleteFile(id);
        return success(true);
    }

    @GetMapping("/{configId}/get/**")
    @PermitAll
    @Operation(summary = "下载文件")
    @Parameter(name = "configId", description = "配置编号", required = true)
    public void getFileContent(HttpServletRequest request,
                               HttpServletResponse response,
                               @PathVariable("configId") Long configId) throws Exception {
        // 获取请求的路径
        String path = StrUtil.subAfter(request.getRequestURI(), "/get/", false);
        if (StrUtil.isEmpty(path)) {
            throw new IllegalArgumentException("结尾的 path 路径必须传递");
        }
        // 解码，解决中文路径的问题 https://gitee.com/zhijiantianya/ruoyi-vue-pro/pulls/807/
        path = URLUtil.decode(path);

        // 读取内容
        byte[] content = fileService.getFileContent(configId, path);
        if (content == null) {
            log.warn("[getFileContent][configId({}) path({}) 文件不存在]", configId, path);
            response.setStatus(HttpStatus.NOT_FOUND.value());
            return;
        }
        writeAttachment(response, path, content);
    }

    @GetMapping("/page")
    @Operation(summary = "获得文件分页")
    @PreAuthorize("@ss.hasPermission('infra:file:query')")
    public CommonResult<PageResult<FileRespVO>> getFilePage(@Valid FilePageReqVO pageVO) {
        PageResult<FileDO> pageResult = fileService.getFilePage(pageVO);
        return success(BeanUtils.toBean(pageResult, FileRespVO.class));
    }

    /**
     * 获取图片信息 (仅适用于 Chevereto)
     * 
     * @param configId 配置ID
     * @param shortCode 图片短码
     * @return 图片信息
     */
    @GetMapping("/image/info")
    @Operation(summary = "获取图片信息", description = "仅适用于 Chevereto 图片管理")
    @PreAuthorize("@ss.hasPermission('infra:file:query')")
    public CommonResult<JSONObject> getImageInfo(
            @RequestParam("configId") Long configId,
            @RequestParam("shortCode") String shortCode) {
        return success(fileService.getImageInfo(configId, shortCode));
    }
    
    /**
     * 检查文件是否存在，通过 MD5 判断
     */
    @GetMapping("/check-exists")
    @Operation(summary = "检查文件是否存在", description = "通过 MD5 判断文件是否已存在")
    public CommonResult<Map<String, Object>> checkFileExists(@RequestParam("md5") String md5) {
        String url = fileService.getFileUrlByMD5(md5);
        Map<String, Object> result = new HashMap<>();
        result.put("exists", StringUtils.isNotEmpty(url));
        if (StringUtils.isNotEmpty(url)) {
            result.put("url", url);
        }
        return success(result);
    }

    /**
     * 压缩图片
     */
    @PostMapping("/compress-image")
    @Operation(summary = "压缩图片", description = "对图片进行压缩处理并返回压缩后的URL")
    public CommonResult<Map<String, Object>> compressImage(@RequestParam("file") MultipartFile file,
                              @RequestParam(value = "maxWidth", required = false, defaultValue = "1280") int maxWidth,
                              @RequestParam(value = "maxHeight", required = false, defaultValue = "720") int maxHeight,
                              @RequestParam(value = "quality", required = false, defaultValue = "0.8") float quality) throws Exception {
        if (!FileMediaUtils.isImage(file.getOriginalFilename())) {
            return success(Map.of("error", "只支持图片文件"));
        }
        
        byte[] originalContent = IoUtil.readBytes(file.getInputStream());
        byte[] compressedContent = FileMediaUtils.compressImage(originalContent, maxWidth, maxHeight, quality);
        
        // 上传压缩后的图片
        String url = fileService.createFile(file.getOriginalFilename(), null, compressedContent);
        
        Map<String, Object> result = new HashMap<>();
        result.put("originalSize", originalContent.length);
        result.put("compressedSize", compressedContent.length);
        result.put("url", url);
        result.put("compressionRatio", ((float) compressedContent.length / originalContent.length) * 100);
        
        return success(result);
    }

}
